﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Globalization;

using XRpgLibrary.ScriptClasses;
using XRpgLibrary.ExtensionMethods;

namespace XLevelEditor.Controls
{
    public partial class ScriptParameterInput : UserControl
    {
        public const int CONTROL_HEIGHT = 23;
        public const int CONTROL_MARGIN = 5;
        public const int LABEL_WIDTH = 115;
        public const int INPUT_WIDTH = 118;

        private string script = null;
        public string Script
        {
            get
            {
                return script;
            }
            set
            {
                script = value;
                InitializeControls();
            }
        }

        private object[] values = null;
        public object[] Values
        {
            get
            {
                return values;
            }
            set
            {
                values = value;
                InitializeControls();
            }
        }

        private Assembly scriptAssembly = null;
        private Assembly ScriptAssembly
        {
            get
            {
                if (scriptAssembly == null)
                    scriptAssembly = Assembly.GetAssembly(typeof(ScriptContext));

                return scriptAssembly;
            }
        }

        private Type ScriptType
        {
            get
            {
                return ScriptAssembly.GetType(Script);
            }
        }

        private static Dictionary<string, string> types = null;
        private static Dictionary<string, string> Types
        {
            get
            {
                if (types == null)
                {
                    types = new Dictionary<string, string>();

                    types.Add("system.string", "string");

                    types.Add("system.char", "char");

                    types.Add("system.boolean", "bool");

                    types.Add("system.sbyte", "sbyte");
                    types.Add("system.byte", "byte");
                    types.Add("system.short", "short");
                    types.Add("system.uint16", "ushort");
                    types.Add("system.int32", "int");
                    types.Add("system.uint32", "uint");
                    types.Add("system.int64", "long");
                    types.Add("system.uint64", "ulong");

                    types.Add("system.single", "single");
                    types.Add("system.double", "double");
                    types.Add("system.decimal", "decimal");

                }

                return types;
            }
        }

        public ScriptParameterInput()
        {
            InitializeComponent();
            this.VScroll = true;
            this.AutoScroll = true;
            this.AutoScrollMargin = new Size(CONTROL_MARGIN, CONTROL_MARGIN);
            this.AutoScrollMinSize = new Size(LABEL_WIDTH + INPUT_WIDTH + CONTROL_MARGIN * 2, 50);
        }

        protected override void OnLoad(EventArgs e)
        {
            InitializeControls();

            base.OnLoad(e);
        }

        private void InitializeControls()
        {
            this.Controls.Clear();
            
            if (string.IsNullOrEmpty(Script))
                return;

            if (ScriptType != null)
            {
                ParameterInfo[] parameters = GetScriptParameters();

                if (EnsureParameterCount(parameters.Length))
                {
                    foreach (ParameterInfo parameter in parameters)
                        MakeControl(
                            parameter.ParameterType,
                            parameter.Name,
                            parameter.DefaultValue == DBNull.Value ? null : parameter.DefaultValue,
                            GetCurrentInputValue());
                }
                else
                {
                    throw new Exception(String.Format(
                        "Parameter mismatch. The script '{0}' has {1} parameters, but there are {2} supplied values.",
                        Script,
                        parameters.Length,
                        Values.Length));
                }
            }
            else
            {
                MakeError(Script);
            }
        }

        private ParameterInfo[] GetScriptParameters()
        {
            ConstructorInfo[] cis = ScriptType.GetConstructors();
            return cis[0].GetParameters();
        }

        private bool EnsureParameterCount(int paramCount)
        {
            if (Values == null)
                return true;

            if (Values.Length == paramCount)
                return true;

            return false;
        }

        private object GetCurrentInputValue()
        {
            if (Values == null)
                return null;

            return Values[(this.Controls.Count / 2)];
        }


        public object[] GetValuesArray()
        {
            List<object> args = new List<object>(this.Controls.Count / 2);
            ParameterInfo[] parameters = GetScriptParameters();

            for (
                int controlIndex = 1, paramIndex = 0, cnt = this.Controls.Count;
                controlIndex < cnt;
                controlIndex += 2, paramIndex++)
            {

                Control ctrl = this.Controls[controlIndex];

                switch (Types[parameters[paramIndex].ParameterType.ToString().ToLower()])
                {
                    case "string":
                        args.Add(((TextBox)ctrl).Text);
                        break;
                    case "char":
                        args.Add(((TextBox)ctrl).Text[0]);
                        break;
                    case "bool":
                        args.Add(((CheckBox)ctrl).Checked);
                        break;
                    case "sbyte":
                        args.Add(((NumericUpDown)ctrl).SByteValue());
                        break;
                    case "byte":
                        args.Add(((NumericUpDown)ctrl).ByteValue());
                        break;
                    case "short":
                        args.Add(((NumericUpDown)ctrl).ShortValue());
                        break;
                    case "ushort":
                        args.Add(((NumericUpDown)ctrl).UShortValue());
                        break;
                    case "int":
                        args.Add(((NumericUpDown)ctrl).IntValue());
                        break;
                    case "uint":
                        args.Add(((NumericUpDown)ctrl).UIntValue());
                        break;
                    case "long":
                        args.Add(((NumericUpDown)ctrl).LongValue());
                        break;
                    case "ulong":
                        args.Add(((NumericUpDown)ctrl).ULongValue());
                        break;
                    case "single":
                        args.Add(((NumericUpDown)ctrl).FloatValue());
                        break;
                    case "double":
                        args.Add(((NumericUpDown)ctrl).DoubleValue());
                        break;
                    case "decimal":
                        args.Add(((NumericUpDown)ctrl).DecimalValue());
                        break;
                }
            }
            return args.ToArray();
        }

        private void MakeError(string scriptName)
        {
            string err = string.Format("The script '{0}' could not be found.", scriptName);
            Label label = new Label();
            label.Text = err;
            label.ForeColor = Color.Maroon;
            label.Size = new Size(this.Width - 23, this.Height - 23);
            label.AutoSize = false;
            label.TextAlign = ContentAlignment.MiddleCenter;
            label.Location = new Point(0,0);

            ToolTip tip = new ToolTip();
            tip.SetToolTip(label, err);

            this.Controls.Add(label);
        }

        private void MakeControl(Type parameterType,  string parameterName, object defaultValue, object currentValue)
        {
            MakeLabel(parameterName);
            MakeInput(parameterType, defaultValue, currentValue);
        }

        private void MakeLabel(string text)
        {
            Label label = new Label();
            label.Text = CapitalizeFirstLetter(text);
            label.Size = new Size(LABEL_WIDTH, CONTROL_HEIGHT);
            label.AutoSize = false;
            label.TextAlign = ContentAlignment.TopRight;
            label.Location = new Point(
                CONTROL_MARGIN,
                (CONTROL_HEIGHT + CONTROL_MARGIN) * (this.Controls.Count / 2));

            ToolTip tip = new ToolTip();
            tip.SetToolTip(label, CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text));

            this.Controls.Add(label);
        }

        /// <summary>
        /// I used to use CultureInfo.CurrentCulture.TextInfo.ToTitleCase
        /// but it takes a sctring like thisIsAVariable and changed it to
        /// Thisisavariable which isn't what I wanted. 
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string CapitalizeFirstLetter(string text)
        {
            if (string.IsNullOrEmpty(text))
                return string.Empty;

            return char.ToUpper(text[0]) + text.Substring(1);
        }

        private void MakeInput(Type type, object defaultValue, object currentValue)
        {
            Control ctrl = InputFactory.CreateInput(type, Types, defaultValue, currentValue);

            ctrl.Size = new Size(INPUT_WIDTH, CONTROL_HEIGHT);
            ctrl.Location = new Point(
                LABEL_WIDTH + CONTROL_MARGIN + CONTROL_MARGIN,
                (CONTROL_HEIGHT + CONTROL_MARGIN) * (this.Controls.Count / 2));


            this.Controls.Add(ctrl);
        }
    }

    static class InputFactory
    {
        public static Control CreateInput(Type type, Dictionary<string,string> Types,  object defaultValue, object currentValue)
        {
            if (Types.ContainsKey(type.ToString().ToLower()))
            {
                switch (Types[type.ToString().ToLower()])
                {
                    case "string":
                        return CreateStringInput((string)defaultValue, (string)currentValue);
                    case "char":
                        return CreateCharInput(ValueOrNull<char>(defaultValue), ValueOrNull<char>(currentValue));
                    case "bool":
                        return CreateBoolInput((bool)defaultValue, currentValue);
                    case "sbyte":
                        return CreateSByteInput(defaultValue, currentValue);
                    case "byte":
                        return CreateByteInput(defaultValue, currentValue);
                    case "short":
                        return CreateShortInput(defaultValue, currentValue);
                    case "ushort":
                        return CreateUShortInput(defaultValue, currentValue);
                    case "int":
                        return CreateIntInput(defaultValue, currentValue);
                    case "uint":
                        return CreateUIntInput(defaultValue, currentValue);
                    case "long":
                        return CreateLongInput(defaultValue, currentValue);
                    case "ulong":
                        return CreateULongInput(defaultValue, currentValue);
                    case "single":
                    case "double":
                    case "decimal":
                        return CreateDecimalInput(type, defaultValue, currentValue);
                }
            }

            return CreateErrorLabel(type);
        }

        private static Control CreateStringInput(string defaultValue, string currentValue)
        {
            TextBox txt = new TextBox();
            txt.Text = currentValue == null ? defaultValue : currentValue;

            ToolTip tip = new ToolTip();
            tip.SetToolTip(txt,
                String.Format("Default Value: '{0}'", defaultValue));

            return txt;
        }

        private static Control CreateCharInput(char? defaultValue, char? currentValue)
        {
            TextBox txt = new TextBox();
            txt.MaxLength = 1;
            txt.Text = currentValue == null ? (defaultValue ?? '\0').ToString() : ((char)currentValue).ToString();

            ToolTip tip = new ToolTip();
            tip.SetToolTip(txt,
                String.Format("Default Value: '{0}'", defaultValue));

            return txt;
        }

        private static Control CreateBoolInput(bool? defaultValue, object currentValue)
        {
            CheckBox chk = new CheckBox();
            chk.Checked = currentValue == null ? (defaultValue ?? false) : (bool)currentValue;

            ToolTip tip = new ToolTip();
            tip.SetToolTip(chk,
                String.Format("Default Value: '{0}'",
                (defaultValue ?? false) ? "Checked" : "Unchecked" ));

            return chk;
        }


        private static Control CreateSByteInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(sbyte.MinValue, sbyte.MaxValue, ValueOrNull<sbyte>(defaultValue), ValueOrNull<sbyte>(currentValue));
        }

        private static Control CreateByteInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(byte.MinValue, byte.MaxValue, ValueOrNull<byte>(defaultValue), ValueOrNull<byte>(currentValue));
        }

        private static Control CreateShortInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(short.MinValue, short.MaxValue, ValueOrNull<short>(defaultValue), ValueOrNull<short>(currentValue));
        }

        private static Control CreateUShortInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(ushort.MinValue, ushort.MaxValue, ValueOrNull<ushort>(defaultValue), ValueOrNull<ushort>(currentValue));
        }

        private static Control CreateIntInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(int.MinValue, int.MaxValue, ValueOrNull<int>(defaultValue), ValueOrNull<int>(currentValue));
        }

        private static Control CreateUIntInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(uint.MinValue, uint.MaxValue, ValueOrNull<uint>(defaultValue), ValueOrNull<uint>(currentValue));
        }

        private static Control CreateLongInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(long.MinValue, long.MaxValue, ValueOrNull<long>(defaultValue), ValueOrNull<long>(currentValue));
        }

        private static Control CreateULongInput(object defaultValue, object currentValue)
        {
            return CreateIntegralInput(ulong.MinValue, ulong.MaxValue, ValueOrNull<ulong>(defaultValue), ValueOrNull<ulong>(currentValue));
        }

        private static Nullable<T> ValueOrNull<T>(object o) where T: struct
        {
            if (o == null)
                return null;

            return (T)o;
        }

        private static Control CreateIntegralInput(decimal min, decimal max, decimal? defaultValue, decimal? currentValue)
        {
            NumericUpDown nud = new NumericUpDown();
            nud.DecimalPlaces = 0;

            nud.Minimum = min;
            nud.Maximum = max;
            nud.Value = currentValue == null ? (defaultValue ?? 0m) : (decimal)currentValue;

            ToolTip tip = new ToolTip();
            tip.SetToolTip(nud,
                String.Format("Default Value: '{0}'\nMin Value: '{1}'\nMax Value: '{2}'",
                    defaultValue,
                    nud.Minimum,
                    nud.Maximum));

            return nud;
        }

        private static Control CreateDecimalInput(Type type, object defaultValue, object currentValue)
        {
            
            NumericUpDown nud = new NumericUpDown();
            nud.DecimalPlaces = 4;
            
            decimal defaultDecimal;
            decimal? currentDecimal;

            if (defaultValue is float)
            {
                defaultDecimal = (decimal)(float)defaultValue;
                currentDecimal = (decimal?)ValueOrNull<float>(currentValue);
            }
            else if (defaultValue is double)
            {
                defaultDecimal = (decimal)(double)defaultValue;
                currentDecimal = (decimal?)ValueOrNull<double>(currentValue);
            }
            else
            {
                defaultDecimal = (decimal)defaultValue;
                currentDecimal = (decimal?)ValueOrNull<decimal>(currentValue);
            }
            
            nud.Minimum = decimal.MinValue;
            nud.Maximum = decimal.MaxValue;
            nud.Value = currentDecimal == null ? defaultDecimal : (decimal)currentDecimal;

            ToolTip tip = new ToolTip();
            tip.SetToolTip(nud, 
                String.Format("Default Value: '{0}'\nMin Value: '{1}'\nMax Value: '{2}'", 
                    defaultValue, 
                    nud.Minimum, 
                    nud.Maximum));

            return nud;
        }

        private static Control CreateErrorLabel(Type type)
        {
            Label lbl = new Label();
            lbl.ForeColor = Color.Maroon;
            string errMsg = String.Format("Not Supported '{0}' ", type);
            lbl.Text = errMsg;

            ToolTip tip = new ToolTip();
            tip.SetToolTip(lbl, errMsg);

            return lbl;
        }
    }
}
